package plp.imperative1.memory;

import java.io.PrintStream;
import java.util.HashMap;
import java.util.Stack;

import plp.expressions2.expression.Id;
import plp.expressions2.expression.Valor;
import plp.expressions2.memory.ContextoExecucao;
import plp.expressions2.memory.VariavelNaoDeclaradaException;

public class ContextoExecucaoImperativa extends ContextoExecucao implements
		AmbienteExecucaoImperativa {

	/**
	 * saída padrão do programa (por padrão aponta para System.out mas pode ser
	 * redirecionada usando o setter)
	 */
	private PrintStream writer;

	/**
	 * A pilha de blocos de contexto.
	 */
	private ListaValor entrada;

	/**
	 * A pilha de blocos de contexto.
	 */
	private ListaValor saida;

	/**
	 * Construtor da classe.
	 */
	public ContextoExecucaoImperativa(ListaValor entrada) {
		super();
		this.entrada = entrada;
		this.saida = new ListaValor();
		this.writer = System.out; // Default output writer
	}
	
	public ContextoExecucaoImperativa(ListaValor entrada, PrintStream writer) {
		super();
		this.entrada = entrada;
		this.saida = new ListaValor();
		this.writer = writer;
	}
	

	private HashMap<Id, Object> monitores = new HashMap<Id, Object>();

	public Valor read() throws EntradaVaziaException {
		if (entrada == null || entrada.getHead() == null) {
			throw new EntradaVaziaException();
		}
		Valor aux = entrada.getHead();
		entrada = (ListaValor) entrada.getTail();
		return aux;
	}

	public ListaValor getSaida() {
		return saida;
	}

	public void write(Valor v) {
		writer.print(v.toString());
	}

	/**
	 * Altera o valor mapeado do id dado.
	 * 
	 * @exception VariavelNaoDeclaradaException
	 *                se n�o existir nenhum valor mapeado ao id dado nesta
	 *                tabela.
	 */
	public void changeValor(Id idArg, Valor valorId)
			throws VariavelNaoDeclaradaException {

		Object result = null;
		Stack<HashMap<Id, Valor>> auxStack = new Stack<HashMap<Id, Valor>>();
		Stack<HashMap<Id, Valor>> stack = this.getPilha();

		while (result == null && !stack.empty()) {
			HashMap<Id, Valor> aux = stack.pop();
			auxStack.push(aux);
			result = aux.get(idArg);
			if (result != null) {
				aux.put(idArg, valorId);
			}
		}
		while (!auxStack.empty()) {
			stack.push(auxStack.pop());
		}
		if (result == null) {
			throw new VariavelNaoDeclaradaException(idArg);
		}
	}

	public Object getMonitorVariavel(Id idArg) {
		return monitores.get(idArg);
	}

	public void mapMonitorVariavel(Id idArg, Object monitor) {
		monitores.put(idArg, monitor);
	}

	public Object clone() {
		// TODO: implement this
		throw new RuntimeException(new CloneNotSupportedException());
	}

	protected ListaValor getEntrada() {
		return entrada;
	}

	protected HashMap<Id, Object> getMonitores() {
		return monitores;
	}

	protected void setMonitores(HashMap<Id, Object> monitores) {
		this.monitores = monitores;
	}

	protected void setSaida(ListaValor saida) {
		this.saida = saida;
	}
	
	protected PrintStream getWriter() {
		return this.writer;
	}

}
